/*
* Handles the layout for a group of pods.
*/
package com.esria.samples.dashboard.managers
{	
	import com.esria.samples.dashboard.view.DragHighlight;
	import com.esria.samples.dashboard.view.Pod;
	
	import flash.events.Event;
	import flash.events.MouseEvent;
	import flash.geom.Point;
	import flash.geom.Rectangle;
	
	import flexlib.mdi.containers.MDIWindowState;
	import flexlib.mdi.events.MDIManagerEvent;
	import flexlib.mdi.events.MDIWindowEvent;
	import flexlib.mdi.managers.MDIManager;
	
	import mx.controls.Alert;
	import mx.core.FlexGlobals;
	import mx.effects.Move;
	import mx.effects.Parallel;
	import mx.effects.Resize;
	import mx.effects.easing.Exponential;
	import mx.events.CloseEvent;
	import mx.events.ResizeEvent;
	import mx.managers.PopUpManager;
	
	import spark.components.SkinnableContainer;
	import spark.events.TitleWindowBoundsEvent;


	public class PodLayoutManager extends MDIManager
	{
		public var id:String; 									// stores view id from xml
				
		private var dragHighlightItems:Array = new Array();		// Stores the highlight items used to designate a drop area.
		
		private var gridPoints:Array = new Array();				// Stores the x,y of each pod in the grid.
		
		private var currentDragPod:Pod;							// The current pod which the user is dragging.
		private var currentVisibleHighlight:DragHighlight;		// The current highlight that is visible while dragging.
		private var currentDropIndex:Number;					// The index of where to drop the pod while dragging.
		private var currentDragPodMove:Move;					// The move effect used to transition the pod after it is released from dragging.
		
		private var parallel:Parallel;							// The main effect container.
		
		private var itemWidth:Number;							// Pod width.
		private var itemHeight:Number;							// Pod height.
		
		private static const POD_GAP:Number = 10;				// The vertical and horizontal gap between pods.				
		
		// mdiwindow close event for processing after confirm close
		private var savedCloseEvent:MDIWindowEvent;
		
		
		public function PodLayoutManager(container:SkinnableContainer):void
		{
			super(container);
		}
		
		// Removes null items from the items array.
		// This should be called only once after all of the items have been added.
		// Null items will be present if a pod was saved at an index that is no longer valid
		// because the number of pods has been reduced in the XML.
		public function removeNullItems():void
		{
			var a:Array = new Array();
			var len:Number = items.length;
			for (var i:Number = 0; i < len; i++)
			{
				if (items[i] != null)
				{
					a.push(items[i]);
				}
			}
			
			items = a;
			
			container.addEventListener(ResizeEvent.RESIZE, updateLayout);
		}
		
		public function addMinimizedItemAt(pod:Pod, index:Number):void
		{	
			if (index == -1)
			{
				index = minimizedWindows.length;
			}
			pod.minimize();
			
			minimizedWindows.setItemAt(pod, index);
			initItem(pod);
		}
		
		public function addItemAt(pod:Pod, index:Number, maximized:Boolean):void
		{	
			if (maximized == true)
			{
				pod.maximize();
			}
			
			if (index == -1)
			{
				index = items.length;
			}			
			
			items[index] = pod;
			initItem(pod);
		}
		
		private function initItem(pod:Pod):void
		{
			// add thru mdi manager add
			add(pod);
			
			// Add a highlight for each pod. Used to show a drop target box.
			var dragHighlight:DragHighlight = new DragHighlight();
			dragHighlight.visible = false;
			dragHighlightItems.push(dragHighlight);
			container.addElement(dragHighlight);			
		}
		
		// handle maximize
		override protected function onMaximizeWindow(event:Event):void
		{	
			if (!event.isDefaultPrevented())
			{			
				super.onMaximizeWindow(event);
			}
		}
		
		// handle minimize
		override protected function onMinimizeWindow(event:Event):void
		{
			if (!event.isDefaultPrevented())
			{					
				var pod:Pod = Pod(event.currentTarget);
				//remove from list of "non minimized" items array
				for(var i:int = 0; i < items.length; i++)
				{
					if (items[i] == pod)
					{
						items.splice(i,1);
						break;
					}
				}
											
				if (isTiled == true)
				{
					minimizedWindows.addItem(pod);

					// added to do sending event done by super, so iframepod will get event
					var winEvent:MDIWindowEvent = event as MDIWindowEvent;
					var mgrEvent:MDIManagerEvent = new MDIManagerEvent(windowToManagerEventMap[winEvent.type], winEvent.window, this, null, null, winEvent.resizeHandle);				
					dispatchEvent(mgrEvent);								
					
					updateLayout(true);	
				}
				else
				{
					super.onMinimizeWindow(event);					
				}
				
			}
		}
		
		// handle restoring
		override protected function onRestoreWindow(event:Event):void
		{
			if (!event.isDefaultPrevented())
			{			
				var pod:Pod = Pod(event.currentTarget);
				
				if (pod.windowState == MDIWindowState.MINIMIZED) 
				{
					// remove it from the array of minimized windows
					removeFromMinimizedList(pod, false);
					// add to array of open / non minimized windows
					items.push(pod);
				}																		
				
				if ( (isTiled == true) && (pod.windowState == MDIWindowState.MINIMIZED) )
				{	
					// added to do sending event done by super, so iframepod will get event
					var winEvent:MDIWindowEvent = event as MDIWindowEvent;
					var mgrEvent:MDIManagerEvent = new MDIManagerEvent(windowToManagerEventMap[winEvent.type], winEvent.window, this, null, null, winEvent.resizeHandle);
					dispatchEvent(mgrEvent);
			
					// Current state is minimized, add it into a relayout of the tiled grid
					updateLayout(true);
				}
				else
				{
					// restore to old size/postion 				
					super.onRestoreWindow(event);				
				}
			}
		}
		
		// prompt for confirm of window close
		override protected function onCloseWindow(event:Event):void
		{
			if (event is MDIWindowEvent)
			{
				// store a copy of the event in for use in handleAlertResponse
				savedCloseEvent = event.clone() as MDIWindowEvent;
							
				var alert:Alert = Alert.show("Close window ?", null, Alert.YES | Alert.NO, null, handleAlertResponse);
				// for mobile, display near top so doesn't appear behind WebViews
				PopUpManager.centerPopUp(alert);
				alert.y = 0;
			}
		}
		
		// handle results of confirm for close
		private function handleAlertResponse(event:CloseEvent):void
		{
			if (event.detail == mx.controls.Alert.YES)
			{
				var mgrEvent:MDIManagerEvent = new MDIManagerEvent(windowToManagerEventMap[savedCloseEvent.type], savedCloseEvent.window, this, null, null, savedCloseEvent.resizeHandle);
				
				mgrEvent.effect = this.effects.getWindowCloseEffect(mgrEvent.window, this);			
				dispatchEvent(mgrEvent);
			}
		}
		
	    override protected function onDragStart(event:Event):void
		{
			super.onDragStart(event);

			// if in tile mode, setup to do dynamic re-layout during drag
		    if (isTiled == true)
		    {
	    		currentDragPod = Pod(event.currentTarget);
	    		var len:Number = items.length;
	    		for (var i:Number = 0; i < len; i++) // Find the current drop index so we have a start point.
	    		{
	    			if (Pod(items[i]) == currentDragPod)
	    			{
	    				currentDropIndex = i;
	    				break;
	    			}
	    		}
	    		
	    		// Use the stage so we get mouse events outside of the browser window.
				FlexGlobals.topLevelApplication.stage.addEventListener(MouseEvent.MOUSE_MOVE, onMouseMove);
		    }
		}
		
		override protected function onDragEnd(event:Event):void
		{
			super.onDragEnd(event);

			if (isTiled == true)
	        {
				FlexGlobals.topLevelApplication.stage.removeEventListener(MouseEvent.MOUSE_MOVE, onMouseMove);
	    		
	    		if (currentVisibleHighlight != null)
	    			currentVisibleHighlight.visible = false;
				    		
	    		
				// sreiner: add check for having gridPoints and drop index
	    		if ( (!isNaN(currentDropIndex)) && (currentDropIndex < gridPoints.length) )
	    		{
		    		var point:Point = Point(gridPoints[currentDropIndex]);
		    		if (point.x != currentDragPod.x || point.y != currentDragPod.y)
		    		{
		    			currentDragPodMove = new Move(currentDragPod);
		    			currentDragPodMove.easingFunction = Exponential.easeOut;
		    			currentDragPodMove.xTo = point.x;
		    			currentDragPodMove.yTo = point.y;
		    			currentDragPodMove.play();
		    		}
	    		}
	        }
		}
		
		// Handles the live resorting of pods as one is dragged.
		private function onMouseMove(e:MouseEvent):void
		{
			if (isTiled == true)
			{			
				var len:Number = items.length;	// Use the items since we can have more pods than highlights when a pod(s) is minimized.
				var dragHighlightItem:DragHighlight;
				var overlapArea:Number = 0; 	// Keeps track of the amount (w *h) of overlap between rectangles.
				var dragPodRect:Rectangle = new Rectangle(currentDragPod.x, currentDragPod.y, currentDragPod.width, currentDragPod.height);
				var dropIndex:Number = -1;		// The new drop index. This will create a range from currentDropIndex to dropIndex for transtions below.
				
				// Loop through the highlights and figure out which one has the greatest amount of overlap with the pod that is being dragged.
				// The highlight with the max overlap will be the drop index.
				for (var i:Number = 0; i < len; i++)
				{
					dragHighlightItem = DragHighlight(dragHighlightItems[i]);
					if (dragHighlightItem != null)
					{
						dragHighlightItem.visible = false;
						var dragHighlightItemRect:Rectangle = new Rectangle(dragHighlightItem.x, dragHighlightItem.y, dragHighlightItem.width, dragHighlightItem.height);
						//sreiner: hitTestObject doesn't hit with most skins in flex4 so using intersects
						if (dragHighlightItemRect.intersects(dragPodRect))
						{
							var intersection:Rectangle = dragHighlightItemRect.intersection(dragPodRect);
							if ((intersection.width * intersection.height) > overlapArea)
							{
								currentVisibleHighlight = dragHighlightItem;
								overlapArea = intersection.width * intersection.height;
								dropIndex = i;
							}
						}
					}
				}
				
				if (currentDropIndex != dropIndex) // Make sure we have a new drop index so we don't create redudant effects.
				{
					if (dropIndex == -1) // User is not over a highlight.
						dropIndex = currentDropIndex;
					
					if (currentDragPodMove != null && currentDragPodMove.isPlaying)
						currentDragPodMove.pause();
					
					if (parallel != null && parallel.isPlaying)
						parallel.pause();
					
					parallel = new Parallel();
					parallel.duration = 1000;
					
					var a:Array = new Array(); // Used to re-order the items array.
					a[dropIndex] = currentDragPod;
					//test currentDragPod.index = dropIndex;
					
					for (i = 0; i < len; i++)
					{
						var targetX:Number;
						var targetY:Number;
						var point:Point;
						var pod:Pod = Pod(items[i]);
						
						var index:Number;
						if (i != currentDropIndex)
						{
							// Find the index to determine the lookup in gridPoints.
							if ((i < currentDropIndex && i < dropIndex) ||
								(i > currentDropIndex && i > dropIndex)) // Below or above the range of dragging.
								index = i;
							else if (i > currentDropIndex && i <= dropIndex) // Drag forwards
								index = i - 1;
							else if (i < currentDropIndex && i >= dropIndex) // Drag backwards
								index = i + 1;
							else
								index = i;
							
							a[index] = pod;
							
							// sreiner: add check for having gridPoints
							if (gridPoints.length > 0)
							{
								point = Point(gridPoints[index]); // Get the x,y coord from the grid.
								
								targetX = point.x;
								targetY = point.y;
								
								if (targetX != pod.x || targetY != pod.y)
								{
									var move:Move = new Move(pod);
									move.easingFunction = Exponential.easeOut;
									move.xTo = targetX;
									move.yTo = targetY;
									parallel.addChild(move);
								}
							}
						}
					}
					
					if (parallel.children.length > 0)
						parallel.play();
				
					currentDropIndex = dropIndex;
					
					// Reassign the items array so the new order is reflected.
					items = a;
				}
				
				// sreiner: add check for null highlight
				if (currentVisibleHighlight != null)
				{
					currentVisibleHighlight.visible = true;
				}
			}
		}
		
		// Lays out the pods, minimized pods and drag highlight items.
		public function updateLayout(tween:Boolean=true):void
		{
			if (parallel != null && parallel.isPlaying)
				parallel.pause();
			
			if (tween)
			{
				parallel = new Parallel();
				parallel.duration = 1000;
			}
			
			if (this.isTiled == true)
			{
				layoutItemsTiled(tween);
			}				
			
			// Layout the minimized items.
			layoutMinimizedItems(tween);
			
			if (parallel != null && parallel.children.length > 0)
				parallel.play();
			
			// Layout the drag highlight items.
			layoutDragHighlights();
		}
		
		// layout open windows/pods items in a tiled grid
		private function layoutItemsTiled(tween:Boolean=true):void
		{
			var len:Number = items.length;
			var sqrt:Number = Math.floor(Math.sqrt(len));
			var numCols:Number = Math.ceil(len / sqrt);
			var numRows:Number = Math.ceil(len / numCols);
			var col:Number = 0;
			var row:Number = 0;
			var pod:Pod;
			itemWidth = Math.round(availablePodWidth / numCols - ((POD_GAP * (numCols - 1)) / numCols));
			itemHeight = Math.round(availablePodHeight / numRows - ((POD_GAP * (numRows - 1)) / numRows));
			
			// Layout the pods.
			for (var i:Number = 0; i < len; i++)
			{			
				if (i % numCols == 0 && i > 0)
				{
					row++;
					col = 0;
				}
				else if (i > 0)
				{
					col++;
				}
				
				var targetX:Number = col * itemWidth;
				var targetY:Number = row * itemHeight;
				
				if (col > 0) 
					targetX += POD_GAP * col;
				if (row > 0) 
					targetY += POD_GAP * row;
				
				targetX = Math.round(targetX);
				targetY = Math.round(targetY);
				
				pod = items[i];
				if (pod.windowState == MDIWindowState.MAXIMIZED)// Window is maximized so do not include in the grid
				{
					if (tween)
					{
						addResizeAndMoveToParallel(pod, parallel, availablePodWidth, availableMaximizedPodHeight, 0, 0);
					}
					else
					{
						pod.width = availablePodWidth;
						pod.height = availableMaximizedPodHeight;
					}
					
					// Move the pod to the top of the z-index. It will not be at the top if we are coming from a saved state
					// and the pod is not the last one.
					container.setElementIndex(pod, container.numElements - 1);				
				}
				else
				{
					if (tween)
					{
						addResizeAndMoveToParallel(pod, parallel, itemWidth, itemHeight, targetX, targetY);
					}
					else
					{
						pod.width = itemWidth;
						pod.height = itemHeight;
						pod.x = targetX;
						pod.y = targetY;
					}
				}
				
				gridPoints[i] = new Point(targetX, targetY);
			}			
		}
		
		// layout minimized windows in a row mini tiles/bars
		private function layoutMinimizedItems(tween:Boolean=true):void
		{
			var len:Number = minimizedWindows.length;
			if (len > 0)
			{
				// Check to see if all of the minimized items will be too wide.
				var totalMinimizedItemWidth:Number = len * TASKBAR_ITEM_WIDTH + (len -1) * TASKBAR_HORIZONTAL_GAP;
				var minimizedItemWidth:Number;
				if (totalMinimizedItemWidth > availablePodWidth) // Items are too wide so resize.
					minimizedItemWidth = Math.round((availablePodWidth - (len - 1) * TASKBAR_HORIZONTAL_GAP) / len);
				else
					minimizedItemWidth = TASKBAR_ITEM_WIDTH;
				
				for (var i:Number = 0; i < len; i++)
				{
					var pod:Pod = Pod(minimizedWindows.getItemAt(i));
					pod.height = pod.minimizeHeight;
					var targetX:Number = i * (TASKBAR_HORIZONTAL_GAP + minimizedItemWidth);
					if (tween)
					{
						addResizeAndMoveToParallel(pod, parallel, minimizedItemWidth, pod.minimizeHeight, targetX, minimizedPodY);
					}
					else
					{
						pod.width = minimizedItemWidth;
						pod.x = targetX;
						pod.y = minimizedPodY;
					}
				}
			}			
		}
		
		// layout drag target background boxes
		private function layoutDragHighlights():void
		{
			var len:Number = dragHighlightItems.length;
			for (var i:Number = 0; i < len; i++)
			{
				var dragHighlight:DragHighlight = DragHighlight(dragHighlightItems[i]);
				if (i > (items.length - 1)) // The corresponding item is minimized so hide the highlights not being used.
				{
					dragHighlight.visible = false;
					dragHighlight.x = 0;
					dragHighlight.y = 0;
					dragHighlight.width = 0;
					dragHighlight.height = 0;
				}
				else
				{
					var point:Point = Point(gridPoints[i]);
					if (point != null)
					{
						dragHighlight.x = point.x;
						dragHighlight.y = point.y;
						dragHighlight.width = itemWidth;
						dragHighlight.height = itemHeight;
						container.setElementIndex(dragHighlight, i); // Move the hightlights to the bottom of the z-index.
					}					
				}
			}			
		}
		
		// Creates a resize and move event and adds them to a parallel effect.
		private function addResizeAndMoveToParallel(target:Pod, parallel:Parallel, widthTo:Number, heightTo:Number, xTo:Number, yTo:Number):void
		{
			var resize:Resize = new Resize(target);
			resize.widthTo = widthTo;
			resize.heightTo = heightTo;
			resize.easingFunction = Exponential.easeOut;
			parallel.addChild(resize);
			
			var move:Move = new Move(target);
			move.xTo = xTo;
			move.yTo = yTo;
			move.easingFunction = Exponential.easeOut;
			parallel.addChild(move);
		}

		
		// Returns the available width for all of the pods.
		private function get availablePodWidth():Number
		{
			return container.width;
		}
		
		// Returns the available height for all of the pods.
		private function get availablePodHeight():Number
		{
			return container.height - TASKBAR_HEIGHT - TASKBAR_PADDING_TOP;
		}
		
		// Returns the available height for a maximized pod.
		private function get availableMaximizedPodHeight():Number
		{
			return container.height;
		}
		
		// Returns the target y coord for a minimized pod.
		private function get minimizedPodY():Number
		{
			return container.height - TASKBAR_HEIGHT;
		}		
        		
	}
}